/*++

Copyright (c) 2007  Microsoft Corporation 

Filename: 

    DiagramGenerator.cs

Abstract: 

    A class to Create a Visio diagram dynamically
     
--*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Threading;
using Visio = Microsoft.Office.Interop.Visio;
using System.Globalization;

namespace NRTMAddIn
{
    internal class DiagramGenerator
    {
        //Stores document reference
        private Document nrtmDoc;

        //Spacing between the machine spaces along X-axis
        double spacingForX;

        //Spacing between the rows along Y-axis
        double spacingForY;

        //Max shapes per page
        const int maxShapesPerPage = 102;

        //Max rows per page
        const int maxRowsPerPage = 6;

        //Max shapes per row
        const int maxShapesPerRow = 17;

        /// <summary>
        /// Create a visio document dynamically
        /// </summary>
        /// <param name="serversList">List of servers to drop on the visio pages</param>
        public void CreateDocument(object serversList)
        {
            StatusDlg statusDlg = null;
                    
            //Object to store current running thread reference
            Thread nrtmDiagramThread;


            //Return if not a valid Visio aaplication
            if (Global.VisioApp == null)
            {
                return;
            }

            try
            {
                Visio.Document currDoc = Global.VisioApp.ActiveDocument;
                Visio.Page currPage = currDoc.Application.ActivePage;

                nrtmDoc = null;

                //Check if current document contains more than 1 page or current page contains any shapes
                if (currDoc.Pages.Count > 1 ||
                    (currPage != null && currPage.Shapes.Count != 0))
                {
                    //Active document is not empty. Create new document for diagram generation

                    //Global.InternalDocOpen = true;

                    // Add the stencil shapes using a template for network diagrams
                    currDoc = Global.VisioApp.Documents.AddEx(Global.AddinTemplate
                        , Visio.VisMeasurementSystem.visMSUS, 0, 0);                   
                }


                //Get the reference on document object
                while (nrtmDoc == null)
                {
                    //Loop till the document create event is called & 
                    //the object is loaded into hash table
                    nrtmDoc = Application.GetDocumentObject(currDoc);
                    //Global.InternalDocOpen = false;
                }

                //Set diagram generation flag on
                nrtmDoc.diagramGenerationOn = true;
                nrtmDoc.StatusWindow.Visible = true;

                //Save the scan option
                nrtmDoc.ipOption = Global.IPOption;

                //Update the menu state
                nrtmDoc.docMenu.NRTMMenu_UIBuilder_RefreshUIObject(Global.MenuStateDiagramCreation);

                // get the status dialog form
                if (nrtmDoc.StatusWindow != null)
                {
                    statusDlg = (StatusDlg)nrtmDoc.StatusWindow.FormToShow;
                }

                if (statusDlg != null)
                {
                    // reset the status
                    statusDlg.ResetStatus();

                    // enable the cancel button at the beginning of all new scans
                    statusDlg.CancelButtonEnabled(true);

                    // notify the beginning of the monitoring
                    statusDlg.UpdateStatus(Global.GetResourceString("StatusDlg_Update_StartDiagram"));
                }

                System.Windows.Forms.Application.DoEvents();

                nrtmDiagramThread = null;
                nrtmDiagramThread = new Thread(new ParameterizedThreadStart(this.CreateDiagram));

                if (nrtmDiagramThread != null)
                {
                    //Assign thread refrence to document object for thread abort
                    nrtmDoc.NrtmDiagramThread = nrtmDiagramThread;

                    nrtmDiagramThread.Name = "NRTMDiagramGenerator";
                    nrtmDiagramThread.Start(serversList);
                }
                
            }
            catch (System.Threading.ThreadAbortException)
            {
                //Ignore thread abort exception
            }
            catch (NrtmException ex)
            {
                //To catch and display exceptions from sub functions
                NrtmException.HandleExceptions(Global.GetResourceString("Exception_MsgBox_Caption"),
                            Global.GetResourceString("Exception_UsrMsg"), ex.InnerException);
                
                //Update the status and reset the diagram flag
                nrtmDoc.StopDiagramGeneration(Global.ThreadStop.Abort);
            }
            catch (Exception ex)
            {
                //To catch exceptions from current functions
                NrtmException.HandleExceptions(Global.GetResourceString("Exception_MsgBox_Caption"),
                            Global.GetResourceString("Exception_UsrMsg"), ex);
            
                //Update the status and reset the diagram flag
                nrtmDoc.StopDiagramGeneration(Global.ThreadStop.Abort);
            }
        }

        /// <summary>
        /// Create a visio diagram dynamically
        /// </summary>
        /// <param name="serversList">List of servers to drop on the visio pages</param>
        public void CreateDiagram(object serversList)
        {
            Global.ThreadStop diagramFailStatus = Global.ThreadStop.Abort;
            try
            {
                if (nrtmDoc == null)
                    return;

                //Convert the object to array list
                ArrayList[] servers = (ArrayList[])serversList;


                // Add this line to provide Undo functionality in Visio.
                int undoScope = Global.VisioApp.BeginUndoScope("Server Diagram");

                // Add the stencil shapes using a template for network diagrams
                Visio.Document activeDocument = nrtmDoc.VisioDocument;
                Visio.Master machineMaster = Global.VisioApp.Documents[Global.AddinStencil].Masters.get_ItemU(Global.ServerMasterName);
                Visio.Master ethernetMaster = Global.VisioApp.Documents[Global.AddinStencil].Masters.get_ItemU(Global.EthernetMasterName);

                //Set diagram generation flag on
                nrtmDoc.diagramGenerationOn = true;

                // Get a reference to the vsoWindow and the vsoPage.
                Visio.Window vsoWindow = Global.VisioApp.ActiveWindow;
                Visio.Page vsoPage = vsoWindow.PageAsObj;

                //Set the page width and height
                vsoPage.PageSheet.get_Cells("PageWidth").FormulaU = "11 in";
                vsoPage.PageSheet.get_Cells("PageHeight").FormulaU = "8.5 in";

                //Total number of servers to draw
                int shapesCount = servers[1].Count;

                //Calulate total no of pages to draw
                int pageCount = shapesCount / maxShapesPerPage;
                int pageModCount = shapesCount % maxShapesPerPage;
                if (pageModCount != 0)
                {
                    pageCount += 1;
                }

                int dropShapeCtr = 0;

                double ethernetPinX = 0.35;
                double ethernetPinY = 4.4;
                string visioUnit = " in";
                double ethernetWidth = 7.6;
                int rowCount = 0;
                int rowModCount = 0;

                int pendingMachineCnt = shapesCount;
                int machineCntCurrPage = 0;

                //Calculate the spacing on the X-axis
                CalculateSpacingX(maxShapesPerRow);
                //Calculate the spacing on the Y-axis
                CalculateSpacingY(maxRowsPerPage);

                for (int pageCtr = 0; pageCtr < pageCount; pageCtr++)
                {
                    //No need to add first page as it always get created while document creation
                    if (pageCtr != 0)
                    {
                        //Add new page
                        vsoPage = activeDocument.Pages.Add();
                    }

                    //Set the page width and height
                    vsoPage.PageSheet.get_Cells("PageWidth").FormulaU = "11 in";
                    vsoPage.PageSheet.get_Cells("PageHeight").FormulaU = "8.5 in";

                    //Machines on current page
                    if (pendingMachineCnt >= maxShapesPerPage)
                    {

                        machineCntCurrPage = maxShapesPerPage;
                        pendingMachineCnt -= maxShapesPerPage;
                    }
                    else
                    {
                        machineCntCurrPage = pendingMachineCnt;
                    }

                    //No of rows on current page
                    rowCount = machineCntCurrPage / maxShapesPerRow;
                    rowModCount = machineCntCurrPage % maxShapesPerRow;
                    if (rowModCount != 0)
                    {
                        rowCount += 1;
                    }

                    //Calculate ethernet width based on number of rows on current page
                    ethernetWidth = ethernetWidth - ((maxRowsPerPage - rowCount) * spacingForY);

                    //Calculate ethernet pinY
                    ethernetPinY += (maxRowsPerPage - rowCount) * (spacingForY / 2);

                    //Drop ethernet shape
                    Visio.Shape ethernetShape = vsoPage.Drop(ethernetMaster, ethernetPinX, ethernetPinY);

                    //Change ethernet width
                    ethernetShape.get_CellsSRC((short)Visio.VisSectionIndices.visSectionObject,
                        (short)Visio.VisRowIndices.visRowXFormOut,
                        (short)Visio.VisCellIndices.visXFormWidth).FormulaU = Convert.ToString(ethernetWidth + visioUnit, CultureInfo.InvariantCulture);

                    //ConfigureEthernetShapes(vsoPage, ethernetShape, ethernetWidth, machineCntLastRow);

                    ConfigureMachineShapes(vsoPage, machineMaster,
                        machineCntCurrPage, ethernetShape, ethernetWidth, servers, dropShapeCtr);

                    //Increment drop counter
                    dropShapeCtr += machineCntCurrPage;

                    System.Windows.Forms.Application.DoEvents();

                }

                Global.VisioApp.EndUndoScope(undoScope, true);

                //Diagram generation completed with no errors.
                diagramFailStatus = Global.ThreadStop.Complete;

                //Update the status and reset the diagram flag
                nrtmDoc.StopDiagramGeneration(diagramFailStatus);
            }
            catch (System.Threading.ThreadAbortException)
            {
                //Ignore thread abort exception
            }
            catch (NrtmException ex)
            {
                //To catch and display exceptions from sub functions
                NrtmException.HandleExceptions(Global.GetResourceString("Exception_MsgBox_Caption"),
                            Global.GetResourceString("Exception_UsrMsg"), ex.InnerException);

                //Update the status and reset the diagram flag
                nrtmDoc.StopDiagramGeneration(diagramFailStatus);
            }
            catch (Exception ex)
            {
                //To catch exceptions from current functions
                NrtmException.HandleExceptions(Global.GetResourceString("Exception_MsgBox_Caption"),
                            Global.GetResourceString("Exception_UsrMsg"), ex);

                //Update the status and reset the diagram flag
                nrtmDoc.StopDiagramGeneration(diagramFailStatus);
            }
        }

        /// <summary>
        /// This method is used to connect machine shapes with connectors
        /// Resizes the machine shapes
        /// </summary>
        /// <param name="vsoPage">reference to our visio page</param>
        /// <param name="shapeMaster">reference to machine shape</param>
        public void ConfigureMachineShapes(Visio.Page vsoPage, 
            Visio.Master shapeMaster,
            int shapeCount,
            Visio.Shape ethernetShape,
            double ethernetWidth,
            ArrayList []servers,
            int beginCount)
        {
            double pinX = 0.8;
            double pinY = 7.1;
            Visio.Shape serverShape = null;
            double connectorY = ethernetWidth - 0.6;
            int shapeCtr = beginCount;

            try
            {
                for (int loopCtr = 0; loopCtr < shapeCount; loopCtr++)
                {

                    if (pinX > 10.4)//new line
                    {
                        pinX = 0.8;
                        pinY -= spacingForY;
                        connectorY -= spacingForY;
                    }
                    if (pinY < 0)//new page
                    {
                        //Process only one page per call
                        break;
                    }

                    serverShape = vsoPage.Drop(shapeMaster, pinX, pinY);

                    //Assign network name and ip address to the server
                    Visio.Cell labelCell;
                    labelCell = serverShape.get_Cells("Prop.NetworkName.Value");
                    labelCell.FormulaU = Global.StringToFormulaForString((string)servers[1][shapeCtr]);
                    labelCell = serverShape.get_Cells("Prop.IPAddress.Value");
                    labelCell.FormulaU = Global.StringToFormulaForString((string)servers[2][shapeCtr]);

                    //Glue connector to shape
                    AddDynamicConnector(vsoPage, ethernetShape, serverShape, connectorY);

                    pinX += spacingForX;
                    shapeCtr++;
                }
            }
            catch (System.Threading.ThreadAbortException)
            {
                //Ignore thread abort exception
            }
            catch (NrtmException ex)
            {
                //Exception from sub function. Pass on to entry point
                throw new NrtmException("", ex.InnerException);
            }
            catch (Exception ex)
            {
                //Exception from current function
                throw new NrtmException("", ex);
            }
        }

        /// <summary>
        /// This method calculates the value of Spacing between the machines along X-axis
        /// </summary>
        /// <param name="NumberOfMachinesPerRow">Number Of Machines Per Row in visio page</param>
        private void CalculateSpacingX(int NumberOfMachinesPerRow)
        {
            try
            {
                if (NumberOfMachinesPerRow == 0)
                {
                    spacingForX = 0.6;
                }
                else
                {
                    int pageWidth = 11;
                    double initialPinX = 0.8;
                    spacingForX = (pageWidth - initialPinX) / NumberOfMachinesPerRow;
                }
            }
            catch (System.Threading.ThreadAbortException)
            {
                //Ignore thread abort exception
            }
            catch (NrtmException ex)
            {
                //Exception from sub function. Pass on to entry point
                throw new NrtmException("", ex.InnerException);
            }
            catch (Exception ex)
            {
                //Exception from current function
                throw new NrtmException("", ex);
            }
        }

        /// <summary>
        /// This method calculates the value of Spacing between the rows along Y-axis
        /// </summary>
        /// <param name="NumberOfRows">NumberOfRows per page</param>
        private void CalculateSpacingY(int NumberOfRows)
        {
            try
            {
                if (NumberOfRows == 0)
                {
                    spacingForY = 1.2;
                }
                else
                {
                    double initialPinY = 7.2;
                    spacingForY = initialPinY / NumberOfRows;
                }
            }
            catch (System.Threading.ThreadAbortException)
            {
                //Ignore thread abort exception
            }
            catch (NrtmException ex)
            {
                //Exception from sub function. Pass on to entry point
                throw new NrtmException("Create Diagram", ex.InnerException);
            }
            catch (Exception ex)
            {
                //Exception from current function
                throw new NrtmException("Create Diagram", ex);
            }
        }

        /// <summary>
        /// Connect server and ethernet shape
        /// </summary>
        /// <param name="vsoPage"></param>
        /// <param name="vsoEthernetShape"></param>
        /// <param name="vsoServer"></param>
        /// <param name="connectorY"></param>
        private static void AddDynamicConnector(Visio.Page vsoPage,
            Visio.Shape vsoEthernetShape,
            Visio.Shape vsoServer,
            double connectorY)
        {
            try
            {
                // Add connector to the page.
                Visio.Shape vsoNextConnector;
                vsoNextConnector = vsoPage.Drop(Global.VisioApp.ConnectorToolDataObject, 0, 0);

                // Connect the begin point to the Connection Point of the Ethernet shape
                Visio.Cell vsoBeginX;
                vsoBeginX = vsoNextConnector.get_CellsSRC(
                    (short)Visio.VisSectionIndices.visSectionObject,
                    (short)Visio.VisRowIndices.visRowXForm1D,
                    (short)Visio.VisCellIndices.vis1DBeginX);

                vsoBeginX.GlueTo(vsoServer.get_CellsSRC(
                    (short)Visio.VisSectionIndices.visSectionObject,
                    (short)Visio.VisRowIndices.visRowXFormOut,
                    (short)Visio.VisCellIndices.visXFormPinX));

                // Connect the end point to the PinX of the server shape
                Visio.Cell vsoEndX;
                vsoEndX = vsoNextConnector.get_CellsSRC(
                    (short)Visio.VisSectionIndices.visSectionObject,
                    (short)Visio.VisRowIndices.visRowXForm1D,
                    (short)Visio.VisCellIndices.vis1DEndX);

                short connectorRow = DiagramGenerator.AddConnectionPointToShape(vsoEthernetShape, connectorY);
                vsoEndX.GlueTo(vsoEthernetShape.get_CellsSRC(
                (short)Visio.VisSectionIndices.visSectionConnectionPts,
                connectorRow,
                (short)Visio.VisCellIndices.visCnnctX));
            }
            catch (System.Threading.ThreadAbortException)
            {
                //Ignore thread abort exception
            }
            catch (NrtmException ex)
            {
                //Exception from sub function. Pass on to entry point
                throw new NrtmException("", ex.InnerException);
            }
            catch (Exception ex)
            {
                //Exception from current function
                throw new NrtmException("", ex);
            }
        }

        /// <summary>
        /// Add connection point to the shape
        /// </summary>
        /// <param name="shape"></param>
        /// <param name="connectorY"></param>
        /// <returns></returns>
        private static short AddConnectionPointToShape(Visio.Shape shape, double connectorY)
        {
            bool isConnectionSectionExist = Convert.ToBoolean(shape.get_SectionExists((short)Visio.VisSectionIndices.visSectionConnectionPts, 0));
            short connectorRow = -1;
            try
            {
                if (!isConnectionSectionExist)
                {
                    //Add connection points section if doesn't exist
                    shape.AddSection((short)Visio.VisSectionIndices.visSectionConnectionPts);
                }
                //Add connection point and set the X,Y co-ordinate
                connectorRow = shape.AddRow((short)Visio.VisSectionIndices.visSectionConnectionPts,
                    (short)Visio.VisRowIndices.visRowLast, (short)Visio.VisRowTags.visTagDefault);
                shape.get_CellsSRC((short)Visio.VisSectionIndices.visSectionConnectionPts, connectorRow,
                    (short)Visio.VisCellIndices.visCnnctX).FormulaU = Convert.ToString(connectorY, CultureInfo.InvariantCulture);
                shape.get_CellsSRC((short)Visio.VisSectionIndices.visSectionConnectionPts, connectorRow,
                    (short)Visio.VisCellIndices.visCnnctY).FormulaU = "Height*0.5";

            }
            catch (System.Threading.ThreadAbortException)
            {
                //Ignore thread abort exception
            }
            catch (NrtmException ex)
            {
                //Exception from sub function. Pass on to entry point
                throw new NrtmException("Add Connection Point", ex.InnerException);
            }
            catch (Exception ex)
            {
                //Exception from current function
                throw new NrtmException("Add Connection Point", ex);
            }
            return connectorRow;
        }
    }
}
